import datetime
import os
import matplotlib.pyplot as plt
import csv

import numpy as np
import statsmodels.api as sm

path1 = "/home/hcy/IdeaProjects/distributed-dpv/code/time/"

#

files = {
    "atla": "atla_time.csv",
    "chic": "chic_time.csv",
    "hous": "hous_time.csv",
    "salt": "salt_time.csv",
    "seat": "seat_time.csv",
    "kans": "kans_time.csv",
    "losa": "losa_time.csv",
    "newy32aoa": "newy32aoa_time.csv",
    "wash": "wash_time.csv",
}


# ==============================================================================================


class Record:
    def __init__(self, path, device_time_file=files):
        self.path = path
        self.device_time_file = device_time_file
        self._id_to_actions = {}
        self._device_to_actions = {}
        self._id_set = set()
        self._device_set = set()
        self._time_record = dict()
        self._action_set = set()
        self.node_count_time = list()
        self.recursive_save_time = list()
        self.time_list = list()

        self.node_action_time = dict()
        self.ignore_id = set()
        self.ignore_action = set()
        self.node_set = set()
        self._read_files()

    def _read_files(self):
        for device, filename in self.device_time_file.items():
            self._read_csv_file(device, os.path.join(self.path, filename))

    def _read_csv_file(self, device, filename):
        if not os.path.exists:
            print("%s不存在" % filename)
            return

        with open(filename, "r", encoding="utf-8") as f:
            reader = csv.reader(f)
            for line in reader:
                if line[0] == "nodeCount":
                    continue
                a = Action(action_id=line[0], node_index=line[1], action_name=line[2],
                           time=line[3], device=device)
                if a.action_name.endswith("start"):
                    a_name = str(a.action_name)[:-5].strip("* ")
                    if a_name == "count":
                        self.ignore_id.add(a.action_id)
                self.node_set.add(a.get_node_name())
                self._id_set.add(a.action_id)
                self._device_set.add(a.device)
                self._action_set.add(a.action_name)
                if a.ignore:
                    self.ignore_action.add(a.action_name)
                self._id_to_actions.setdefault(a.action_id, dict())
                self._id_to_actions[a.action_id].setdefault(a.action_name, list())
                self._id_to_actions[a.action_id][a.action_name].append(a)

                self._device_to_actions.setdefault(a.device, dict())
                self._device_to_actions[a.device].setdefault(a.action_id, dict())
                self._device_to_actions[a.device][a.action_id].setdefault(a.action_name, list())
                self._device_to_actions[a.device][a.action_id][a.action_name].append(a)

    def get_action_list(self, name, device):
        key_list = self._id_to_actions.keys()
        sorted(key_list)
        res = []

        for key in key_list:

            if name not in self._id_to_actions[key]:
                continue
            for l in self._id_to_actions[key][name]:
                if l.get_node_name() != device:
                    continue

                res.append(l.time)
        return res

    def read_csv_file_node_count(self, filename):
        if not os.path.exists:
            print("%s不存在" % filename)
            return

        with open(filename, "r", encoding="utf-8") as f:
            reader = csv.reader(f)
            for line in reader:
                if line[0] != "nodeCount":
                    continue
                # print(line[1], line[2])
                self.node_count_time.append(int(line[1]) / 1000000.0)
                self.recursive_save_time.append(int(line[2]) / 1000000.0)

    def analyse_data(self):
        time_sum_dict = dict()
        time_list_dict = dict()
        t_time_list = list()
        id_list = sorted(list(self._id_set))
        for _id in id_list:
            if _id in self.ignore_id:
                continue
            start_action = []
            end_action = []
            actions = []

            start_node = set()
            end_node = set()
            nodes = set()
            devices = dict()

            a_name = ""

            recv_and_send_time = dict()

            # for d in self._device_set:
            #     recv_and_send_time.setdefault(d, dict())
            #     devices.setdefault(d, dict())

            for action_name, action_list in self._id_to_actions[_id].items():
                for action in action_list:

                    nodes.add(action.get_node_name())
                    # recv_and_send_time[action.device].setdefault(action.node_index, dict())
                    recv_and_send_time.setdefault(action.get_node_name(), dict())
                    if action.action_name.endswith("receive"):
                        recv_and_send_time[action.get_node_name()].setdefault("receive", list())
                        recv_and_send_time[action.get_node_name()]["receive"].append(action.time)

                    elif action.action_name.endswith("send") or action.action_name.endswith("finish"):
                        recv_and_send_time[action.get_node_name()].setdefault("send", list())
                        recv_and_send_time[action.get_node_name()]["send"].append(action.time)

                    if not action.action_name.startswith("*"):
                        self._time_record.setdefault(action.device, dict())
                        self._time_record[action.device].setdefault(action.node_index, dict())
                        self._time_record[action.device][action.node_index].setdefault(action.action_name, list())
                        self._time_record[action.device][action.node_index][action.action_name].append(action.time)
                if action_name.endswith("start"):
                    start_action.extend(action_list)
                    for action in action_list:
                        start_node.add(action.get_node_name())
                        a_name = str(action.action_name)[:-5].strip("* ")

                elif action_name.endswith("finish"):
                    end_action.extend(action_list)
                    for action in action_list:
                        end_node.add(action.get_node_name())
                actions.append(action_name)
            mid_node = nodes.difference(start_node).difference(end_node)
            node_time_dict = {}
            for node, item in recv_and_send_time.items():
                if "receive" in item and "send" in item:
                    s = max(item["send"])
                    r = get_max_num_under_flag(item["receive"], s)
                    node_time_dict[node] = s - r
            print("action id:%s" % _id)
            print("action name:%s" % a_name)

            if not start_action:
                print("action %s 没有起始数据" % _id)
                continue

            print("action start node:%s" % start_node)
            print("action mid node:%s" % mid_node)
            print("action end node:%s" % end_node)
            total_time = get_i2_total_time(node_time_dict)

            print("total_time: %s ms" % total_time)
            t_time_list.append(total_time)

            for node, time in node_time_dict.items():
                print("%s time: %s ms" % (node, time))
            print()

        self.time_list = t_time_list
        return t_time_list

    def show_action_time(self):
        for action in self._action_set:
            if action.startswith("*"):
                continue
            plt.figure(figsize=(12, 12))
            plt.title(action)
            cdf_y_axis()
            cdf_x_axis()
            m = 0
            for device, nodes in self._time_record.items():
                for node, actions in self._time_record[device].items():
                    if action in actions:
                        m = max(np.quantile(actions[action], 0.92), m)

            for device, nodes in self._time_record.items():
                for node, actions in self._time_record[device].items():
                    if action in actions:
                        cdf_plot(actions[action], m, device + "-" + node)
                        print(device + "-" + node + " " + action)
                        stat(actions[action])
            # cdf_show()
            plt.tight_layout()
            action = str(action)
            if "/" in action:
                action = action.replace("/", " p ")
            plt.savefig('./pic/' + action + '.png')
            cdf_show()

    def show_device_time(self):
        i = 1

        for device, nodes in self._time_record.items():
            for node, actions in self._time_record[device].items():
                plt.figure(figsize=(12, 12))

                plt.title(device + node)
                cdf_y_axis()
                cdf_x_axis()
                m_a = 0
                for action, action_time in actions.items():
                    if action in self.ignore_action:
                        continue
                    m_a = max(m_a, np.quantile(action_time, 0.92))
                for action, action_time in actions.items():
                    if action in self.ignore_action:
                        continue
                    cdf_plot(action_time, m_a, action)
                i += 1
                # cdf_show()
                plt.tight_layout()
                plt.savefig('./pic/' + device + node + '.png')
                cdf_show()

    def get_device_set(self):
        return self._device_set

    def write_record(self, title, *elements):
        path = os.path.join('pic/', title)
        if not os.path.exists(path):
            os.mkdir(path)

        with open(os.path.join(path, "time.txt"), "w") as f:
            for node in self.node_set:
                ls = []
                for element in elements:
                    ls.append(self.get_action_list(element, node))
                if not all(ls):
                    continue
                plt.figure(figsize=(12, 12))
                plt.title(node + " " + title)
                m = np.quantile(max(ls, key=lambda l: max(l)), 0.95)
                cdf_x_axis()
                cdf_y_axis()

                for i in range(len(elements)):
                    e = elements[i]
                    cdf_plot(ls[i], m, e)
                    stat(ls[i], node + " " + e)
                    f.write(node + " " + e + '\n')
                    f.write("min: %.4f ms, max: %.4f ms, med: %.4f ms, avg: %.4f ms, 90%%quantile: %.4f ms\n" %
                            (min(ls[i]), max(ls[i]), np.median(ls[i]), np.mean(ls[i]), np.quantile(ls[i], 0.9)))
                    f.write("\n")
                f.write('\n\n' + "*" * 35 + '\n\n')

                plt.savefig(os.path.join(path, node) + ".png")
                plt.show()


class Action:

    def __init__(self, action_id, node_index, action_name, time, device):
        self.action_name = action_name
        self.action_id = action_id
        self.node_index = node_index
        self.ignore = action_name.startswith("*")
        self.time = float(time) if self.ignore else float(time) / 1000000.0
        self.device = device

    def get_node_name(self) -> str:
        return "%s-%s" % (self.device, self.node_index)

def get_max_num_under_flag(l, num):
    l.sort()
    last = l[0]
    for n in l:
        if n > num:
            break
        last = n
    return last

# ==============================================================================================

def get_i2_total_time(time_dict: dict):
    def get_time(device):
        if device in time_dict:
            return time_dict[device]
        return 0

    return get_time("seat-0") + get_time("salt-0") + \
           max((get_time("kans-0") + get_time("hous-1") + get_time("atla-0")),
               (get_time("losa-0") + get_time("hous-0") + max(get_time("atla-0"), get_time("kans-0")))) + \
           get_time("chic-0") + get_time("newy32aoa-0") + get_time("wash-0")


def cdf_plot(l, max_x, label, _plot=plt):
    ecdf = sm.distributions.ECDF(l)

    x = plt.np.linspace(0, max_x)
    y = ecdf(x)
    _plot.plot(x, y, linewidth='2', label=label)
    # cdf_y_axis()
    # cdf_x_axis(max_x / 10.0)
    # _plot.xlim((0, max_x))
    _plot.xlabel('time/ms')
    _plot.legend(loc='lower right')


def cdf_show():
    plt.tight_layout()
    plt.show()


def cdf_y_axis():
    miloc = plt.MultipleLocator(0.1)
    # pic.ylim((0, 1))
    al = plt.gca()
    al.yaxis.set_minor_locator(miloc)
    al.grid(axis="y", which="minor")


def cdf_x_axis():
    plt.grid(axis="x", which="major")


def stat(t_list, info=None):
    if info:
        print(info)
    print("count: %s min: %.4f ms, max: %.4f ms, med: %.4f ms, avg: %.4f ms, 90%%quantile: %.4f ms" %
          (len(t_list), min(t_list), max(t_list), np.median(t_list), np.mean(t_list), np.quantile(t_list, 0.9)))
    print()


if __name__ == "__main__":
    r1 = Record(path1)
    #
    t1 = r1.analyse_data()
    # for device in r1.node_set:
    #     l = r1.get_action_list("*bdd load % map size", device)
    #     plt.figure(figsize=(12, 12))
    #     plt.title(device + " cib num")
    #     plt.plot(l)
    #     plt.savefig('pic/' + device + ' bdd map size.png')
    #     plt.show()
    #     stat(l)

    # r1.write_record("load compare", "count load", "count load after merge")
    # r1.write_record("save compare", "count save", "count save after merge(without merge)", "count save after merge",
    #                 "merge time",)
    # r1.write_record("save compare", "count save", "count save after merge(without merge)",
    #                 "merge time", "bdds to array")
    # r1.write_record("save compare", "count save")
    # r1.write_record("serialization compare", "serialization", "serialization after merge", "deserialization",
    #                 "deserialization after merge")

    # read_files(path2)
    # t2 = analyse_data()

    r1.show_device_time()
    r1.show_action_time()
    plt.title = "total_time"
    cdf_y_axis()
    cdf_x_axis()
    cdf_plot(t1, 5, "total_time")
    # # # cdf_plot(t2, 500, "after")
    plt.tight_layout()
    plt.savefig('./pic/total_time.png')
    cdf_show()
    print("total_time:")
    stat(t1)
    # stat(t2)
